package cn.blnp.net.flowable.transfer.sequence.condition;

import cn.hutool.core.map.MapUtil;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.HistoryService;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.Task;
import org.flowable.task.api.TaskQuery;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;

/**
 * <h3>顺序流-条件顺序流</h3>
 *
 * @author <a href="mailto:blnp.yibin@qq.com">lyb</a>
 * @version 1.0
 * @since 2025/5/19 15:00
 */
@Slf4j
@SpringBootTest
public class ConditionSeqTest {

    @Resource
    private RepositoryService repositoryService;
    @Resource
    private RuntimeService runtimeService;
    @Resource
    private TaskService taskService;
    @Resource
    private HistoryService historyService;

    /**
     *  <h2>条件顺序流</h2>
     *  <p>
     *   &emsp;&emsp;顾名思义，条件顺序流（conditional sequence flow）需要满足一定的条件才能被执行。我们可以为从网关、活动、事件离开的顺序流设定规则条件，使工作流引擎在执行网关、活动的后续拆分路线时，
     *   可通过评估连线上的条件来选择路径。在标准顺序流上设置一个条件表达式（condition expression）来决定下一步流出的目标，就构成了一个条件顺序流。当流程离开一个节点时，工作流引擎会计算其每个外出顺序流上
     *   的条件表达式，得到boolean类型的结果。当条件表达式的执行结果为true时，工作流引擎将选择该外出顺序流。当有多条顺序流被选中时，会创建多条分支，流程会以并行方式继续流转。<br/>
     *   &emsp;&emsp;<b style="color:red;">需要注意的是,</b>当条件顺序流搭配网关使用时，网关会以特定的方式处理顺序流上的条件，处理方式与网关类型相关。定义格式如下所示：
     *   <pre>
     *       {@code
     *          <sequenceFlow id="seqFlow" sourceRef="theStart" targetRef="theTask">
     *              <conditionExpression xsi:type="tFormalExpression">
     *                  <![CDATA[${totalPrice > 10000}]]>
     *              </conditionExpression>
     *          </sequenceFlow>
     *       }
     *   </pre>
     *   &emsp;&emsp;因为目前Flowable只支持tFormalExpression，所以需要把xsi:type="tFormalExpression"添加到conditionExpression中。条件表达式放在$后的大括号中，只能使用UEL（Java EE规范的一部分），表达式
     *   的计算结果需要返回boolean值，否则会在解析表达式时抛出异常。Flowable支持两种UEL表达式：值表达式和方法表达式。
     *   <ol>
     *       <li>
     *           <b>值表达式：</b>默认所有流程变量都可以在表达式中使用，所有Spring Bean（在Spring环境中）也可以在表达式中使用，如${isDeptLeader}、${***Bean.***Property}等。
     *       </li>
     *       <li>
     *           <b>方法表达式：</b>方法表达式用于调用一个方法，可以传递参数（也可以不传递）。传递的参数可以是常量值，也可以是表达式，它们会被自动解析。当调用一个无参数的方法时，需要在方法名后添加空括号，
     *           如${userBean.isEnable()}。
     *       </li>
     *   </ol>
     *  </p>
     *  <h3>流程案例演示</h3>
     *  <p>
     *      该流程为采购申请流程。由员工提交采购申请，根据采购金额做出判断：如果采购金额小于1万元，则由财务经理审批；如果采购金额达到或超过1万元，则由财务主管审批。
     *      <b>流程定义文件：</b>{@link flow/conditionalSequenceFlow.bpmn20.xml}<br/>
     *      <b>流程预览图：</b><br/>
     *      <img src="https://cdn4.winhlb.com/2025/05/19/682adb0691cad.png" />
     *  </p>
     **/
    @Test
    public void test() {
        Deployment deploy = repositoryService.createDeployment()
                .name("条件顺序流演示流程")
                .addClasspathResource("flow/conditionalSequenceFlow.bpmn20.xml")
                .deploy();
        log.info("部署流程定义：{}", deploy.getId());
        ProcessDefinition procDef = repositoryService.createProcessDefinitionQuery()
                .deploymentId(deploy.getId())
                .singleResult();
        log.info("流程定义编号：{}", procDef.getId());
        //启动流程实例
        ProcessInstance procInst = runtimeService.startProcessInstanceById(procDef.getId());
        TaskQuery taskQuery = taskService.createTaskQuery()
                .processInstanceId(procInst.getId());

        //查询采购申请任务
        Task userApplyTask = taskQuery.singleResult();
        //设置流程变量
        Map<String, Object> varMap = MapUtil.builder(new HashMap<String, Object>())
                .put("totalPrice", 15000)
                .build();
        //完成采购申请任务
        taskService.complete(userApplyTask.getId(), varMap);
        //查询审批任务
        Task approveTask = taskQuery.singleResult();
        log.info("审批任务taskId：{}，节点名称：{}", approveTask.getId(), approveTask.getName());
        //完成审批任务
        taskService.complete(approveTask.getId());

        /**
         *  演示结果：
         *  2025-05-19 15:20:23.110  INFO 62436 --- [           main] c.b.n.f.t.s.condition.ConditionSeqTest   : 部署流程定义：ba032903-3481-11f0-abe6-50ebf6ec8f6d
         * 2025-05-19 15:20:23.114  INFO 62436 --- [           main] c.b.n.f.t.s.condition.ConditionSeqTest   : 流程定义编号：conditionalSequenceFlow:1:ba573c76-3481-11f0-abe6-50ebf6ec8f6d
         * 2025-05-19 15:20:23.313  INFO 62436 --- [           main] c.b.n.f.t.s.condition.ConditionSeqTest   : 审批任务taskId：ba7db040-3481-11f0-abe6-50ebf6ec8f6d，节点名称：财务主管审批
         **/
    }
}
